﻿using System.Collections.Generic;
using System.Linq;
using Windows.System;
using LinkPad.Helpers;
using LinkPad.Shapes;
using Windows.Devices.Input;
using Windows.Foundation;
using Windows.UI;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Input;
using Windows.UI.Xaml.Media;

namespace LinkPad.Handlers
{
    class LinkEditor : ICanvasActionPublicHandler, IHasColor, ISubEditor
    {
        #region Enumerations

        private enum DragType
        {
            DragNone,
            DragLine,
            DragSource,
            DragTarget
        }

        #endregion

        #region Nested types

        private class SelectedLinkInfo
        {
            public Color Color;

            public double OrigX1;
            public double OrigY1;
            public double OrigX2;
            public double OrigY2;
        }

        #endregion

        #region Fields

        private readonly ShapeManager _shapeManager;
        private readonly Canvas _canvas;

        private readonly Color _selectedLinkColor = Colors.GreenYellow;

        private Point _pressPoint;
        private uint _penId;

        private DragType _dragType;
        private const double DragThreshold = 4;
        private const double DragPointThreashold = 16;

        private readonly Dictionary<ArrowedLink, SelectedLinkInfo> _selectedLinks = new Dictionary<ArrowedLink, SelectedLinkInfo>();

        #endregion

        #region Constructors

        public LinkEditor(ShapeManager shapeManager)
        {
            _shapeManager = shapeManager;
            _canvas = _shapeManager.Canvas;
            MultiSelectStatus = MultiSelectStatus.NonMulti;
        }

        #endregion

        #region Properties

        #region IHasColor members

        public bool HasColor
        {
            get { return FirstSelectedLinkInfo != null; }
        }

        public Color MainColor
        {
            get
            {
                return FirstSelectedLinkInfo == null ? new Color() : FirstSelectedLinkInfo.Color;
            }
            set
            {
                foreach (var v in _selectedLinks)
                {
                    v.Value.Color = value;
                }
            }
        }

        #endregion

        #region ISubEditor members

        public Editor Parent
        {
            get;
            set;
        }

        public bool HasSelected
        {
            get { return _selectedLinks.Count > 0; }
        }

        #endregion

        public MultiSelectStatus MultiSelectStatus { get; set; }

        private ArrowedLink SingleSelectedLink
        {
            get
            {
                return _selectedLinks.Count == 1 ? _selectedLinks.Keys.First(link => true) : null;
            }
        }

        private SelectedLinkInfo SingleSelectedLinkInfo
        {
            get
            {
                return _selectedLinks.Count == 1 ? _selectedLinks.Values.First(linkInfo => true) : null;
            }
        }

        private SelectedLinkInfo FirstSelectedLinkInfo
        {
            get
            {
                return _selectedLinks.Count > 0 ? _selectedLinks.Values.First(linkInfo => true) : null;
            }
        }

        #endregion

        #region Methods

        #region ICanvasActionPublicHandler members

        public void HandlePointerPressed(object sender, PointerRoutedEventArgs e)
        {
            var pt = e.GetCurrentPoint(_canvas);

            TrySelect(pt.Position, e.KeyModifiers == VirtualKeyModifiers.Control);

            if (_selectedLinks.Count == 0)
            {
                return;
            }

            var pointerDevType = e.Pointer.PointerDeviceType;

            if (pointerDevType == PointerDeviceType.Pen ||
                pointerDevType == PointerDeviceType.Mouse && pt.Properties.IsLeftButtonPressed
                || pointerDevType == PointerDeviceType.Touch)
            {
                _pressPoint = pt.Position;

                if (MultiSelectStatus == MultiSelectStatus.NonMulti && SingleSelectedLink != null)
                {
                    var x1 = SingleSelectedLink.X1;
                    var y1 = SingleSelectedLink.Y1;
                    var x2 = SingleSelectedLink.X2;
                    var y2 = SingleSelectedLink.Y2;

                    var d1 = GeometryHelper.Distance(x1, y1, pt.Position.X, pt.Position.Y);
                    var d2 = GeometryHelper.Distance(x2, y2, pt.Position.X, pt.Position.Y);
                    var dls = GeometryHelper.DistanceLineSegmentToPoint(x1, y1, x2, y2, pt.Position.X, pt.Position.Y);

                    if (d1 < d2 && d1 < DragPointThreashold)
                    {
                        _dragType = DragType.DragSource;
                    }
                    else if (d2 < d1 && d2 < DragPointThreashold)
                    {
                        _dragType = DragType.DragTarget;
                    }
                    else if (dls < DragThreshold)
                    {
                        _dragType = DragType.DragLine;
                        SingleSelectedLinkInfo.OrigX1 = x1;
                        SingleSelectedLinkInfo.OrigY1 = y1;
                        SingleSelectedLinkInfo.OrigX2 = x2;
                        SingleSelectedLinkInfo.OrigY2 = y2;
                    }
                }
                else
                {
                    _dragType = DragType.DragLine;
                    foreach (var p in _selectedLinks)
                    {
                        var link = p.Key;
                        var info = p.Value;
                        info.OrigX1 = link.X1;
                        info.OrigY1 = link.Y1;
                        info.OrigX2 = link.X2;
                        info.OrigY2 = link.Y2;
                    }
                }

                _penId = pt.PointerId;
            }
            e.Handled = true;
        }

        public void HandlePointerMoved(object sender, PointerRoutedEventArgs e)
        {
            if (e.Pointer.PointerId != _penId || _dragType == DragType.DragNone)
            {
                return;
            }

            var pt = e.GetCurrentPoint(_canvas);
            switch (_dragType)
            {
                case DragType.DragLine:
                    {
                        foreach (var l in _selectedLinks)
                        {
                            var link = l.Key;
                            var info = l.Value;
                            var deltaX = pt.Position.X - _pressPoint.X;
                            var deltaY = pt.Position.Y - _pressPoint.Y;

                            link.X1 = info.OrigX1 + deltaX;
                            link.Y1 = info.OrigY1 + deltaY;
                            link.X2 = info.OrigX2 + deltaX;
                            link.Y2 = info.OrigY2 + deltaY;
                        }
                    }
                    break;
                case DragType.DragSource:
                    if (SingleSelectedLink != null)
                    {
                        SingleSelectedLink.X1 = pt.Position.X;
                        SingleSelectedLink.Y1 = pt.Position.Y;
                    }
                    break;
                case DragType.DragTarget:
                    if (SingleSelectedLink != null)
                    {
                        SingleSelectedLink.X2 = pt.Position.X;
                        SingleSelectedLink.Y2 = pt.Position.Y;
                    }
                    break;
            }
        }

        public void HandlePointerReleased(object sender, PointerRoutedEventArgs e)
        {
            _dragType = DragType.DragNone;
            _penId = 0;
        }

        public void HandlePointerExited(object sender, PointerRoutedEventArgs e)
        {
        }

        public void HandleCanvasTapped(object sender, TappedRoutedEventArgs e)
        {
            // not doing anything here to avoid interfering with multiple selection
        }

        public void HandleCanvasDoubleTapped(object sender, DoubleTappedRoutedEventArgs e)
        {
        }

        public void HandleDeactivated()
        {
            DeselectAll();
        }

        public void DeselectAll()
        {
            foreach (var p in _selectedLinks)
            {
                var link = p.Key;
                var info = p.Value;
                // NOTE link are always set to single colour
                link.Color = info.Color;
            }
            _selectedLinks.Clear();
        }

        public void DeleteAllSelected()
        {
            foreach (var selected in _selectedLinks.Keys)
            {
                _shapeManager.RemoveShape(selected);
            }
        }

        #endregion

        private void TrySelect(Point pt, bool multiSelectKey = false)
        {
            // select the closest link
            var closestDist = double.MaxValue;
            ArrowedLink closestLink = null;
            foreach (var shape in _shapeManager.Shapes)
            {
                var link = shape as ArrowedLink;
                if (link == null)
                {
                    continue;
                }

                var dist = link.DistanceToPoint(pt);
                if (dist < closestDist && dist <= DragThreshold)
                {
                    closestDist = dist;
                    closestLink = link;
                }
            }

            if (multiSelectKey)
            {
                MultiSelectStatus = MultiSelectStatus.Multi;
            }
            else if (closestLink == null)
            {
                MultiSelectStatus = MultiSelectStatus.Empty;
            }
            else if (!_selectedLinks.ContainsKey(closestLink))
            {
                DeselectAll();
                MultiSelectStatus = MultiSelectStatus.NonMultiResetAll;
            }

            if (closestLink != null)
            {
                SelectLink(closestLink);
            }
        }

        private void SelectLink(ArrowedLink link)
        {
            if (_selectedLinks.ContainsKey(link))
            {
                return;
            }
            _selectedLinks.Add(link, new SelectedLinkInfo
            {
                Color = link.Color
            });
            link.ShaftStroke = new SolidColorBrush(_selectedLinkColor);
            link.ArrowHeadFill = link.ShaftStroke;
        }

        #endregion
    }
}
